# coding=utf-8
'''
Created on May 16, 2009

@license: License: LGPL
@copyright: (c) 2009, dogsing.cn
@author: ant-man(Bin Zhao)
'''
from mocLexer import mocLexer
from plyParser import PLYParser, Coord, ParseError
from types import StringType
import mocAst
import ply.yacc
import re

class MocParser(PLYParser):

    '''
    Parser of Mini-Object-C
    @author: ant-man(Bin Zhao)
    '''

    def __init__(self, lex_optimize=True, lextab='moclextab',
            yacc_optimize=True, yacctab='mocyacctab', yacc_debug=False):
        """ Create a new mocParser.
        
            Some arguments for controlling the debug/optimization
            level of the parser are provided. The defaults are 
            tuned for release/performance mode. 
            The simple rules for using them are:
            *) When tweaking mocParser/mocLexer, set these to False
            *) When releasing a stable parser, set to True
            
            lex_optimize:
                Set to False when you're modifying the lexer.
                Otherwise, changes in the lexer won't be used, if
                some lextab.py file exists.
                When releasing with a stable lexer, set to True
                to save the re-generation of the lexer table on 
                each run.
            
            lextab:
                Points to the lex table that's used for optimized
                mode. Only if you're modifying the lexer and want
                some tests to avoid re-generating the table, make 
                this point to a local lex table file (that's been
                earlier generated with lex_optimize=True)
            
            yacc_optimize:
                Set to False when you're modifying the parser.
                Otherwise, changes in the parser won't be used, if
                some parsetab.py file exists.
                When releasing with a stable parser, set to True
                to save the re-generation of the parser table on 
                each run.
            
            yacctab:
                Points to the yacc table that's used for optimized
                mode. Only if you're modifying the parser, make 
                this point to a local yacc table file
                        
            yacc_debug:
                Generate a parser.out file that explains how yacc
                built the parsing table from the grammar.
        """

        self.moclex = mocLexer(error_func=self._lex_error_func)

        self.moclex.build(optimize=lex_optimize, lextab=lextab, debug=yacc_debug)
        self.tokens = self.moclex.tokens_nc

        rules_with_opt = [
            'expression',
            'params_list',
            'statement_list',
            'translation_unit',
        ]

        for rule in rules_with_opt:
            self._create_opt_rule(rule)

        self.mocparser = ply.yacc.yacc(module=self, start='translation_unit_opt',
                                       debug=yacc_debug, optimize=yacc_optimize, tabmodule=yacctab)

    def parse(self, text, filename='', debuglevel=0):
        """ Parses moc code and returns an AST.
        
            text:
                A string containing the moc source code
            
            filename:
                Name of the file being parsed (for meaningful error messages)
            
            debuglevel:
                Debug level to yacc
        """
        self.moclex.filename = filename
        self.moclex.reset_lineno()
        return self.mocparser.parse(text, lexer=self.moclex, debug=debuglevel)

    def _lex_error_func(self, msg, line, column):
        self._parse_error(msg, self._coord(line, column))

    def p_error(self, p):
        if p:
            self._parse_error('before: %s' % p.value, self._coord(p.lineno))
        else:
            self._parse_error('At end of input', '')

    def p_empty(self, p):
        """ empty : """
        p[0] = None

    ##
    ## Precedence and associativity of operators
    ##
    precedence = (
        ('left', 'LOR'),
        ('left', 'LAND'),
        ('left', 'OR'),
        ('left', 'XOR'),
        ('left', 'AND'),
        ('left', 'EQ', 'NE'),
        ('left', 'GT', 'GE', 'LT', 'LE'),
        ('left', 'RSHIFT', 'LSHIFT'),
        ('left', 'PLUS', 'MINUS'),
        ('left', 'TIMES', 'DIVIDE', 'MOD')
    )

    ##
    ## Grammar productions
    ## Implementation of the BNF defined in syntax
    ##

    def p_translation_unit_1(self, p):
        """ translation_unit  : statement
        """
        # Note: external_declaration is already a list
        #
        p[0] = mocAst.FileAST([p[1]] if p[1] else [])

    def p_translation_unit_2(self, p):
        """ translation_unit  : translation_unit statement
        """
        if p[2] :
            # consider the situation, when received a empty expression, like:
            # ; //just a semicolon
            p[1].module.append(p[2])
        p[0] = p[1]

    # Note: this doesn't create an AST node, but a list of AST 
    # nodes that will be used as the statement list of a compound
    #
    def p_statement_list(self, p):
        """ statement_list  : statement 
                            | statement_list statement
        """
        if len(p) == 2: # single stmt
            p[0] = [p[1]] if p[1] else []
        else:
            p[0] = p[1] + ([p[2]] if p[2] else [])

    def p_statement(self, p):
        """  statement   : expression_statement 
                         | compound_statement
                         | selection_statement
                         | iteration_statement    
                         | jump_statement
                         | decl_statement
        """
        p[0] = p[1]

    def p_expression_statement(self, p):
        """ expression_statement : expression_opt SEMI """
        p[0] = p[1]

    def p_expression(self, p):
        """ expression   : unary_expression assignment_operator expression
                         | conditional_expression
                         | func_closure
        """
        if len(p) == 2:
            p[0] = p[1]
        else:
            p[0] = mocAst.Assignment(p[2], p[1], p[3], p[1].coord)

    def p_unary_expression_1(self, p):
        """ unary_expression  : postfix_expression """
        p[0] = p[1]

    def p_unary_expression_3(self, p):
        """ unary_expression  : PLUSPLUS unary_expression 
                              | MINUSMINUS unary_expression
                              | unary_operator unary_expression
        """
        p[0] = mocAst.UnaryOp(p[1], p[2], p[2].coord)

    def p_postfix_expression_1(self, p):
        """ postfix_expression  : postfix_expression PLUSPLUS 
                                | postfix_expression MINUSMINUS
        """
        p[0] = mocAst.UnaryOp('p' + p[2], p[1], p[1].coord)

    def p_postfix_expression_2(self, p):
        """ postfix_expression  : postfix_expression PERIOD identifier"""
        p[0] = mocAst.ObjectRef(p[1], p[3], p[1].coord)

    def p_postfix_exptession_3(self, p):
        """ postfix_expression  : primary_expression """
        p[0] = p[1]

    def p_postfix_exptession_4(self, p):
        """ postfix_expression  : postfix_expression LBRACKET params_list RBRACKET """
        p[3].actionT = "index"
        p[0] = mocAst.CollectionRef(p[1], p[3], p[1].coord)

    def p_postfix_exptession_5(self, p):
        """ postfix_expression  : postfix_expression LPAREN params_list RPAREN
                                | postfix_expression LPAREN RPAREN
        """
        p[0] = mocAst.FuncCall(p[1], p[3] if len(p) == 5 else None)

    def p_identifier(self, p):
        """ identifier  : ID """
        p[0] = mocAst.ID(p[1], self._coord(p.lineno(1)))

    def p_primary_expression_1(self, p):
        """ primary_expression  : identifier """
        p[0] = p[1]

    def p_primary_expression_2(self, p):
        """ primary_expression  : STRING_LITERAL """
        p[0] = mocAst.Constant('string', p[1], self._coord(p.lineno(1)))

    def p_primary_expression_3(self, p):
        """ primary_expression  : constant """
        p[0] = p[1]

    def p_primary_expression_4(self, p):
        """ primary_expression  : LPAREN expression RPAREN
                                | LBRACKET params_list RBRACKET
                                | LBRACE params_list RBRACE """
        if p[1] == "[" :
            p[2].actionT = "list"
        if p[1] == "{" :
            p[2].actionT = "dict"
        p[0] = p[2]

    def p_constant_1(self, p):
        """ constant  : INTEGER"""
        p[0] = mocAst.Constant('int', p[1], self._coord(p.lineno(1)))

    def p_constant_2(self, p):
        """ constant  : FLOAT """
        p[0] = mocAst.Constant('float', p[1], self._coord(p.lineno(1)))

    def p_constant_3(self, p):
        """ constant  : TRUE 
                      | FALSE
        """
        ret = p[1].lower() == "true"
        p[0] = mocAst.Constant('bool', ret, self._coord(p.lineno(1)))

    def p_constant_4(self, p):
        """ constant  : NULL """
        p[0] = mocAst.Constant('none', p[1], self._coord(p.lineno(1)))

    # Rule of the unary operator,
    # which contains: &, +, -, ~, !
    def p_unary_operator(self, p):
        """ unary_operator  : AND
                            | PLUS
                            | MINUS
                            | NOT
                            | LNOT
        """
        p[0] = p[1]

    def p_assignment_operator(self, p):
        """ assignment_operator : EQUALS
                                | XOREQUAL   
                                | TIMESEQUAL  
                                | DIVEQUAL    
                                | MODEQUAL    
                                | PLUSEQUAL   
                                | MINUSEQUAL  
                                | LSHIFTEQUAL 
                                | RSHIFTEQUAL 
                                | ANDEQUAL    
                                | OREQUAL
                                | COLON
        """
        p[0] = p[1]

    def p_conditional_expression(self, p):
        """ conditional_expression  : binary_expression"""
        p[0] = p[1]

    def p_func_closure(self, p):
        """ func_closure : DEF LPAREN params_list_opt RPAREN compound_statement """
        p[0] = mocAst.FuncDef(None, p[3], p[5], self._coord(p.lineno(1)))

    def p_binary_expression_1(self, p):
        """ binary_expression   : unary_expression 
                                | binary_expression TIMES binary_expression
                                | binary_expression DIVIDE binary_expression
                                | binary_expression MOD binary_expression
                                | binary_expression PLUS binary_expression
                                | binary_expression MINUS binary_expression
                                | binary_expression RSHIFT binary_expression
                                | binary_expression LSHIFT binary_expression
                                | binary_expression AND binary_expression
                                | binary_expression OR binary_expression
                                | binary_expression XOR binary_expression """
        if len(p) == 2:
            p[0] = p[1]
        else:
            p[0] = mocAst.ArithOp(p[2], p[1], p[3], p[1].coord)

    def p_binary_expression_2(self, p):
         """ binary_expression   : binary_expression LT binary_expression
                                 | binary_expression LE binary_expression
                                 | binary_expression GE binary_expression
                                 | binary_expression GT binary_expression
                                 | binary_expression EQ binary_expression
                                 | binary_expression NE binary_expression"""
         p[0] = mocAst.RelOp(p[2], p[1], p[3], p[1].coord)

    def p_binary_expression_3(self, p):
         """ binary_expression   : binary_expression LAND binary_expression
                                 | binary_expression LOR binary_expression"""
         p[0] = mocAst.LogicalOp(p[2], p[1], p[3], p[1].coord)

    def p_compound_statement(self, p):
        """ compound_statement : LBRACE statement_list_opt RBRACE """
        p[0] = mocAst.Compound(p[2], self._coord(p.lineno(1)))

    def p_selection_statement_1(self, p):
        """ selection_statement : IF LPAREN expression RPAREN compound_statement """
        p[0] = mocAst.If(p[3], p[5], None, self._coord(p.lineno(1)))

    def p_selection_statement_2(self, p):
        """ selection_statement : IF LPAREN expression RPAREN compound_statement ELSE compound_statement """
        p[0] = mocAst.If(p[3], p[5], p[7], self._coord(p.lineno(1)))

    def p_iteration_statement_1(self, p):
        """ iteration_statement : WHILE LPAREN expression RPAREN compound_statement """
        p[0] = mocAst.While(p[3], p[5], self._coord(p.lineno(1)))

    def p_iteration_statement_2(self, p):
        """ iteration_statement : FOR LPAREN expression_opt SEMI expression_opt SEMI expression_opt RPAREN compound_statement """
        p[0] = mocAst.For(p[3], p[5], p[7], p[9], self._coord(p.lineno(1)))

    def p_iteration_statement_3(self, p):
        """ iteration_statement : FOR LPAREN identifier IN identifier RPAREN compound_statement """
        p[0] = mocAst.ForEach(p[3], p[5], p[7], self._coord(p.lineno(1)))

    def p_jump_statement_2(self, p):
        """ jump_statement  : BREAK SEMI """
        p[0] = mocAst.Break(self._coord(p.lineno(1)))

    def p_jump_statement_3(self, p):
        """ jump_statement  : CONTINUE SEMI """
        p[0] = mocAst.Continue(self._coord(p.lineno(1)))

    def p_jump_statement_4(self, p):
        """ jump_statement  : RETURN expression SEMI  
                            | RETURN SEMI 
        """
        p[0] = mocAst.Return(p[2] if len(p) == 4 else None, self._coord(p.lineno(1)))

    def p_decl_statement(self, p):
        """ decl_statement  : funcdef """
        p[0] = p[1]

    def p_funcdef(self, p):
        """ funcdef  : DEF identifier LPAREN params_list_opt RPAREN compound_statement """
        p[0] = mocAst.FuncDef(p[2], p[4], p[6], self._coord(p.lineno(1)))

    def p_params_list(self, p):
        """ params_list  : expression
                         | params_list COMMA expression """
        if len(p) == 2: # single expr
            p[0] = mocAst.ParamList([p[1]], p[1].coord)
        else:
            p[1].params.append(p[3])
            p[0] = p[1]


if __name__ == "__main__":
    from cn.pigersing.moc.lang.mocInstance import inst_list
    from mocTranslate import MocTranslator
    import sys
    import time

    t1 = time.time()
    parser = MocParser(lex_optimize=False, yacc_debug=True, yacc_optimize=False)
    print 'Parsing cost %.3f sec' % (time.time() - t1)

    src = r"""
        y = 1++;
        1.ab = c;
        obj.a = 5566.123e2 ;
        obj.foo = def ( sxt ) {
            this.a = 7788;
            this.stsx;
            return ok;
        } ;
        def x (){
            def ss(){
                sss ;
            }
            y = 5 ;
            return def (){ return y; };
        }
        for ( el in obj ) {
            el = 64;
        }
        while(foo()) {
            a ++ ;
        }
        d = {"124":5, a:6, e: d};
        c = d[1];
        d = e[a];
        f = d[1:a];
        f[1] = 6;
        b = [1,2,3,4,5] ;
        foo.a.c = b ;
        a = foo(a, b);
        b = obj.foo.foobar(as,bb);
        for(a = 0; a < 5; a++) {
            x += 1 ;
            if (x >2 ) {
                break;
            }
            for(; a < 5; a++) {
                x += 1 ;
            } 
        } 
        
    """

    src2 = r"""
    a = 12 ;
    for (i = 0; i < 5; i++) {
        a--;
    }
    print (a);
    """

    try:
        t = parser.parse(src2, 'x.moc', debuglevel=0)
        t.show(showcoord=True)
        s = MocTranslator(sys.stdout)
    except ParseError, e :
        print >> sys.stderr, 'error occurs ', e

#    for att in dir(parser) :
#        if att.startswith('p_') :
#            a = getattr(parser, att)
#            print a.__doc__


